//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: keyhandler.c 817 2007-08-24 16:31:08Z roger $
//

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#define LOG_DOMAIN "core"
#include "keyhandler.h"
#include "core.h"
#include "punc.h"
#include "us.h"
#include "ui.h"
#include "im_methods.h"
#include "phrase.h"
#include "log.h"

#include <string.h>
#include <stdio.h>

/**
 * @defgroup KeyHandler Key Handler
 * @{
 */

#define DEBUG_KEYHANDLER 0	//arren

#define _MOD_FILTER ( IM_ALT_GRAPH_MASK | IM_ALT_MASK | IM_CTRL_MASK | IM_META_MASK | IM_SHIFT_MASK )

typedef struct _KeyStroke KeyStroke;

struct _KeyStroke {
	int keyCode;
	int modifier;
	void (*handler)(iml_session_t *s, IMKeyListEvent *ev);
	KeyStroke *next;
};

static void handle_trigger_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_shift_trigger_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_next_im_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_prev_im_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_tcsc_toggle_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_chnmode_toggle_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_width_toggle_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_puncce_toggle_key(iml_session_t *s, IMKeyListEvent *ev);
static void handle_punctuation_toggle_key(iml_session_t *s, IMKeyListEvent *ev);
static int handle_punctuation_key_event(iml_session_t *s, IMKeyListEvent *ev);
static int handle_phrase_key(iml_session_t *s, IMKeyListEvent *ev);


typedef struct {
	KeyStroke *keys;
} KeyHandlerData;


/***************************************************************************/
/**
 * @brief Initialises Key Handler
 *
 * @param desktop   current IMLDesktop
 * @see keyhandler_done()
 */
void
keyhandler_init(iml_desktop_t *desktop)
{
	DataPerDesktop *dtdata = (DataPerDesktop *)desktop->specific_data;
	KeyHandlerData *d = (KeyHandlerData *)dtdata->keyhandler_data;
	if (d) return;

	d = (KeyHandlerData *)malloc(sizeof(KeyHandlerData));
	memset(d, 0, sizeof(KeyHandlerData));
	dtdata->keyhandler_data = d;

	KeyStroke *newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK;
	newkey->keyCode = IM_VK_SPACE;
	newkey->handler = handle_trigger_key;
	newkey->next = NULL;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK | IM_ALT_MASK;
	newkey->keyCode = IM_VK_EQUALS;
	newkey->handler = handle_next_im_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK;
	newkey->keyCode = IM_VK_SHIFT;
	newkey->handler = handle_next_im_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK | IM_ALT_MASK;
	newkey->keyCode = IM_VK_MINUS;
	newkey->handler = handle_prev_im_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_SHIFT_MASK;
	newkey->keyCode = IM_VK_CONTROL;
	newkey->handler = handle_prev_im_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK | IM_ALT_MASK;
	newkey->keyCode = IM_VK_BACK_SLASH;
	newkey->handler = handle_tcsc_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_SHIFT_MASK;
	newkey->keyCode = IM_VK_SPACE;
	newkey->handler = handle_width_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK;
	newkey->keyCode = IM_VK_M;
	newkey->handler = handle_chnmode_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;
	
	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK;
	newkey->keyCode = IM_VK_COMMA;
	newkey->handler = handle_width_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;

	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK;
	newkey->keyCode = IM_VK_PERIOD;
	newkey->handler = handle_puncce_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;
	
	newkey = (KeyStroke *)malloc(sizeof(KeyStroke));
	newkey->modifier = IM_CTRL_MASK | IM_ALT_MASK;
	newkey->keyCode = IM_VK_COMMA;
	newkey->handler = handle_punctuation_toggle_key;
	newkey->next = d->keys;
	d->keys = newkey;
}


/***************************************************************************/
/**
 * @brief Destroys Key Handler
 *
 * @param desktop   current IMLDesktop
 * @see keyhandler_init()
 */
void
keyhandler_done(iml_desktop_t *desktop)
{
	DataPerDesktop *dtdata = (DataPerDesktop *)desktop->specific_data;
	KeyHandlerData *d = (KeyHandlerData *)dtdata->keyhandler_data;
	KeyStroke *foo, *key;

	if (!d) return;
	key = d->keys;

	while (key) {
		foo = key;
		key = key->next;
		free(foo);
	}
	free(d);
	dtdata->keyhandler_data = NULL;
}


/***************************************************************************/
/**
 * @brief Handles incoming key event
 *
 * @param s     current IMLSession
 * @param ev    incoming IMKeyListEvent
 */
void
unihan_handle_key_event(iml_session_t *s, IMKeyListEvent *ev)
{
	DataPerDesktop *dtdata = (DataPerDesktop *)s->desktop->specific_data;
	DataPerSession *scdata = (DataPerSession *)s->specific_data;
	KeyHandlerData *d = (KeyHandlerData *)dtdata->keyhandler_data;
	KeyStroke *key = d->keys;
	iml_inst *lp;

#if DEBUG
    char logmsg[256];
    strcpy(logmsg, "handling key ");
	if (ev->keylist->modifier & 0x80000000)
        strcat(logmsg, "release ");
    if (ev->keylist->modifier & IM_BUTTON1_MASK)
        strcat(logmsg, "<NumLock>");
    if (ev->keylist->modifier & IM_ALT_GRAPH_MASK)
        strcat(logmsg, "<AltGr>");
    if (ev->keylist->modifier & IM_ALT_MASK)
        strcat(logmsg, "<Alt>");
    if (ev->keylist->modifier & IM_META_MASK)
        strcat(logmsg, "<Meta>");
    if (ev->keylist->modifier & IM_CTRL_MASK)
        strcat(logmsg, "<Ctrl>");
    if (ev->keylist->modifier & IM_SHIFT_MASK)
        strcat(logmsg, "<Shift>");
    unihan_debug("%s%d (%c)", logmsg, ev->keylist->keyCode, ev->keylist->keyChar);
#endif

#if TIMEBOMB
        if (check_expired(s->desktop)) {
                notify_expired(s);
                goto key_not_handled;
        }
#endif

    int modifier = ev->keylist->modifier & _MOD_FILTER;

	/* check for single Shift key here */
	if ((ev->keylist->keyCode == IM_VK_SHIFT) && (modifier == 0) && (scdata->shift_key_state == 0)) {
		/* Shift pressed */
		scdata->shift_key_state = 1;
		return;
	} else if (scdata->shift_key_state == 1) {
		scdata->shift_key_state = 0;
		if ((ev->keylist->keyCode == IM_VK_SHIFT) && (ev->keylist->modifier & 0x80000000)) {
			/* Shift released without pressing other keys */
			handle_shift_trigger_key(s, ev);
			return;
		}
	}

	/* drop all other keyreleases */
	if (ev->keylist->modifier & 0x80000000) {
        // FIXME: don't return the keyrelease event
        // because it is unexpected by the client and treats it
        // as a keypress event!
		/* goto key_not_handled; */
        return;
    }
	
	// FIXME: are we in phrase mode?
	if (scdata->phrase_state == PHRASE_STATE_SELECTING) {
		// FIXME: hand off to phrase handler
		if (handle_phrase_key(s, ev))
			return;
		phrase_matcher_hide_results(s);
		scdata->phrase_state = PHRASE_STATE_IDLE;
		if (ev->keylist->keyCode == IM_VK_ESCAPE) {
			// we eat the Escape key
			return;
		}
	}

	while (key) {
		if (key->modifier == modifier &&
		    key->keyCode == ev->keylist->keyCode &&
		    key->handler != NULL) {
			key->handler(s, ev);
			return;
		}
		key = key->next;
	}

	if (scdata->conv_on != 1) {
		lp = s->If->m->iml_make_keypress_inst(s, ev->keylist);
		s->If->m->iml_execute(s, &lp);
		return;
	}

    // nothing ate the keystroke, so pass along to the current IM
    if (im_propagate_key_event(s, ev)) {
        if (scdata->phrase_state == PHRASE_STATE_SELECTING) {
            phrase_matcher_show_results(s);
        }
        return;
    } else {
        if (scdata->punctuation) {
            if (handle_punctuation_key_event(s, ev)) {
                return;
            }
        }
    }

key_not_handled:
	// not handled by the IM!
	lp = s->If->m->iml_make_keypress_inst(s, (IMKeyEventStruct *)ev->keylist);
	s->If->m->iml_execute(s, &lp);
}


void
unihan_handle_hotkey_event(iml_session_t *s, IMKeyListEvent *ev)
{
	DataPerDesktop *dtdata = (DataPerDesktop *)s->desktop->specific_data;
	DataPerSession *scdata = (DataPerSession *)s->specific_data;
	KeyHandlerData *d = (KeyHandlerData *)dtdata->keyhandler_data;
	KeyStroke *key = d->keys;
	iml_inst *lp;

#if DEBUG
    char logmsg[256];
    strcpy(logmsg, "handling hotkey ");
	if (ev->keylist->modifier & 0x80000000)
        strcat(logmsg, "release ");
    if (ev->keylist->modifier & IM_BUTTON1_MASK)
        strcat(logmsg, "<NumLock>");
    if (ev->keylist->modifier & IM_ALT_GRAPH_MASK)
        strcat(logmsg, "<AltGr>");
    if (ev->keylist->modifier & IM_ALT_MASK)
        strcat(logmsg, "<Alt>");
    if (ev->keylist->modifier & IM_META_MASK)
        strcat(logmsg, "<Meta>");
    if (ev->keylist->modifier & IM_CTRL_MASK)
        strcat(logmsg, "<Ctrl>");
    if (ev->keylist->modifier & IM_SHIFT_MASK)
        strcat(logmsg, "<Shift>");
    unihan_debug("%s%d (%c)", logmsg, ev->keylist->keyCode, ev->keylist->keyChar);
#endif

    return;
}


/***************************************************************************/
static void
handle_trigger_key(iml_session_t *s, IMKeyListEvent *ev)
{
	DataPerSession *ds = (DataPerSession *)s->specific_data;
	
	if (ds->conv_on != 0)
		conversion_off(s);
	else
		conversion_on(s);
#if 0
#if NEW_PROPERTY
#else
	conversion_off(s);
#endif
#endif
}

static void
handle_shift_trigger_key(iml_session_t *s, IMKeyListEvent *ev)
{
	DataPerSession *ds = (DataPerSession *)s->specific_data;
	
	if (ds->conv_on == 1) {
		conversion_temp_off(s);
	} else {
		conversion_temp_on(s);
	}
}

/***************************************************************************/
static void
handle_next_im_key(iml_session_t *s, IMKeyListEvent *ev)
{
	im_change_to_next_im(s);
}

/***************************************************************************/
static void
handle_prev_im_key(iml_session_t *s, IMKeyListEvent *ev)
{
	im_change_to_prev_im(s);
}

/***************************************************************************/
static void 
handle_tcsc_toggle_key(iml_session_t *s, IMKeyListEvent *ev)
{
#if 0
	UnihanPropertyArgList prop;

	DataPerDesktop *dd = (DataPerDesktop *)s->desktop->specific_data;
	UnihanTCSCMode newmode;

	prop = im_get_runtime_property(s, UNIHAN_RUNTIME_PROPERTY_TCSC, 0);	
	
	if(prop){
		newmode = prop->UnihanProperty.rt_property.value;
        newmode++;
        if (newmode > UNIHAN_TCSC_TO_TRADITIONAL)
                newmode = UNIHAN_TCSC_NO_CONV;
		prop->UnihanProperty.rt_property.value = newmode;
		tcsc_set(s, newmode);
	}
#endif

#if NEW_PROPERTY
#else
        DataPerDesktop *dd = (DataPerDesktop *)s->desktop->specific_data;
        UnihanTCSCMode newmode = dd->tcsc;

        newmode++;
        if (newmode > UNIHAN_TCSC_TO_TRADITIONAL)
                newmode = UNIHAN_TCSC_NO_CONV;

        tcsc_set(s, newmode);
#endif
}


/***************************************************************************/
static void
handle_chnmode_toggle_key(iml_session_t *s, IMKeyListEvent *ev)
{
#if NEW_PROPERTY
		char *key = "/PropertyRoot/state";
		DataPerSession *scdata = (DataPerSession *)s->specific_data;
		
		im_propagate_property_toggled_event(s, scdata->currIM, key);
#endif
}

static void 
handle_width_toggle_key(iml_session_t *s, IMKeyListEvent *ev)
{
#if NEW_PROPERTY
		char *key = "/PropertyRoot/width";
		DataPerSession *scdata = (DataPerSession *)s->specific_data;
		
		im_propagate_property_toggled_event(s, scdata->currIM, key);
#else
        toggle_width(s);
#endif
}

static void
handle_puncce_toggle_key(iml_session_t *s, IMKeyListEvent *ev)
{
#if NEW_PROPERTY
		char *key = "/PropertyRoot/puncce";
		DataPerSession *scdata = (DataPerSession *)s->specific_data;
		
		im_propagate_property_toggled_event(s, scdata->currIM, key);
#else
		toggle_puncce(s);
#endif
}

/***************************************************************************/
static void 
handle_punctuation_toggle_key(iml_session_t *s, IMKeyListEvent *ev)
{
        toggle_punctuation_keyboard(s);
}


/***************************************************************************/
static int
handle_punctuation_key_event(iml_session_t *s, IMKeyListEvent *ev)
{
	IMKeyEventStruct *k = (IMKeyEventStruct *)ev->keylist;
	DataPerSession *scdata = (DataPerSession *)s->specific_data;
	char *commit_u8 = NULL;
	
	if ((k->modifier & _MOD_FILTER) != 0)
		return FALSE;

	/* Escape */
	if (k->keyCode == IM_VK_ESCAPE) {
		punc_keyboard_hide(s);
		scdata->punctuation = 0;
		return TRUE;
	}

	commit_u8 = find_punctuation(k->keyChar);
	if (!commit_u8) {
		return FALSE;
        } else {
                UnihanIM uim;
                uim.privateData = s;
                commit_string(&uim, commit_u8);
        }

	punc_keyboard_hide(s);
	scdata->punctuation = 0;
	return TRUE;
}


/***************************************************************************/
static int
handle_phrase_key(iml_session_t *s, IMKeyListEvent *ev)
{
	DataPerSession *ds = (DataPerSession *)s->specific_data;
	IMKeyEventStruct *k = (IMKeyEventStruct *)ev->keylist;

	// only handle Shift-1 .. 9, prev-page, next-page
	// we should only be here with shift key pressed

	int row;
	char *result;
	switch (k->keyCode) {
		case IM_VK_1 ... IM_VK_9:
			if ((k->modifier & _MOD_FILTER) != IM_SHIFT_MASK) return FALSE;
			row = k->keyCode - IM_VK_1;
			result = (char *)phrase_matcher_get_result(s, row);
			if (result) {
				ds->phrase_state = PHRASE_STATE_IDLE;
				phrase_matcher_hide_results(s);
				UnihanIM uim;
				uim.privateData = s;
				commit_string(&uim, result);
				match_phrase(&uim, result);
				if (ds->phrase_state == PHRASE_STATE_SELECTING)
					phrase_matcher_show_results(s);
			}
			return TRUE;

		case IM_VK_COMMA:
		case IM_VK_OPEN_BRACKET:
			if ((k->modifier & _MOD_FILTER) != IM_SHIFT_MASK) return FALSE;
		case IM_VK_PAGE_UP:
			phrase_matcher_prev_page(s);
			return TRUE;

		case IM_VK_PERIOD:
		case IM_VK_CLOSE_BRACKET:
			if ((k->modifier & _MOD_FILTER) != IM_SHIFT_MASK) return FALSE;
		case IM_VK_PAGE_DOWN:
			phrase_matcher_next_page(s);
			return TRUE;

		case IM_VK_SPACE:
			if ((k->modifier & _MOD_FILTER) == 0) {
				UnihanIM uim;
				uim.privateData = s;
				commit_string(&uim, " ");
				return TRUE;
			}

		default:
			return FALSE;
	}
	return FALSE;
}

/** @} */
// vi:ts=4:sw=4:nowrap:cin:expandtab
